package cn.itcast.redisdemo.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.itcast.redisdemo.pojo.CacheBreakDownDto;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;

/**
 * 热点key 缓存击穿问题解决
 * 处理 需要处理 缓存击穿的工具类
 */
@Component
@Slf4j
public class CacheRebuildUtils {
    @Resource
    private RedisUtil redisUtil;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private RedissonClient redissonClient;

    private final static String rebuild_CACHE_BREAK_DOWN = "rebuild_cacheBreakDown";

    /**查询热点redis缓存（若逻辑过期，则线程安全重建），前提：必须是逻辑过期
     * @param cachePrefix 缓存前缀
     * @param id 缓存后缀（主键id）
     * @param type 类型
     * @param dbFallback 重建缓存函数
     * @param <R> 结果类型
     * @param <ID> 主键id 类型
     * @return
     */
    public <R, ID> R queryHotSpotCacheData(String cachePrefix, ID id, Class<R> type, Function<ID, R> dbFallback) {
        String s = redisUtil.get(cachePrefix + id);
        if (StrUtil.isBlank(s)) {
            // 抛出异常。因为key是逻辑过期，理论上不会出现这种情况
        }
        CacheBreakDownDto dto = JSONUtil.toBean(s, CacheBreakDownDto.class);
        DateTime expirationDate = DateUtil.date(dto.getExpiration());
        R r = BeanUtil.toBean((JSONObject) dto.getData(), type);
        DateTime now = DateUtil.date();

        // 未过期直接返回
        if (DateUtil.compare(now, expirationDate) < 0) {
            return r;
        }
        //过期了, 交给另外一个线程去重建热点key
        RLock lock = redissonClient.getLock(rebuild_CACHE_BREAK_DOWN + id);
        // 去重建。拿不到锁，也不要紧，返回老的缓存key。
        if (lock.tryLock()) {
            try {
                // 执行重建缓存key
                threadPoolExecutor.execute(() -> {
                    R apply = dbFallback.apply(id);
                    redisUtil.set(cachePrefix + id, JSONUtil.toJsonStr(apply));
                });
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return r;
    }


}
